﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SCRUM.Core.BusinessObjects;
using System.Data.Common;
using System.Web.Security;
using System.Text.RegularExpressions;
using System.Configuration.Provider;
using System.Configuration;
using SCRUM.Core.MembershipProviders;


namespace SCRUM.Core.MembershipProviders
{
    /// <summary>
    /// Membership provider for database. 
    /// </summary>
    public partial class MembershipDBDataProvider : MembershipDataProvider
    {
        /// <summary>
        /// Create user
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="email"></param>
        /// <param name="passwordQuestion"></param>
        /// <param name="passwordAnswer"></param>
        /// <param name="isApproved"></param>
        /// <param name="providerUserKey"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            return (MembershipUser) CreateNewUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, String.Empty, 1, 1, 1, out status);
        }

        
        private const int DefaultMinRequiredPasswordLength = 8;
        private const string EmailPattern = @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$";

        #region UnusedPart
        private string applicationName, connectionString;
        //private NameValueCollection configuration;

        /// <summary>
        /// The name of the application using the custom membership provider. This provider ignores this value.
        /// </summary>
        /// <value></value>
        /// <returns>The name of the application using the custom membership provider.</returns>
        public override string ApplicationName
        {
            get { return this.applicationName; }
            set { this.applicationName = value; }
        }

        /// <summary>
        /// Indicates that this membership provider allows users to reset their passwords.
        /// </summary>
        /// <value></value>
        /// <returns>true</returns>
        public override bool EnablePasswordReset
        {
            get { return true; }
        }

        /// <summary>
        /// Indicates that this the membership provider does not allow users to retrieve their passwords.
        /// </summary>
        /// <value></value>
        /// <returns>false</returns>
        public override bool EnablePasswordRetrieval
        {
            get { return false; }
        }

        /// <summary>
        /// Account lockout is not supported by this provider.
        /// </summary>
        /// <value></value>
        /// <returns>0</returns>
        public override int MaxInvalidPasswordAttempts
        {
            get { return 0; }
        }

        /// <summary>
        /// Account lockout is not supported by this provider.
        /// </summary>
        /// <value></value>
        /// <returns>0</returns>
        public override int PasswordAttemptWindow
        {
            get { return 0; }
        }

        /// <summary>
        /// Passwor reset questions are not supported by this provider.
        /// </summary>
        /// <value></value>
        /// <returns>false</returns>
        public override bool RequiresQuestionAndAnswer
        {
            get { return false; }
        }

        /// <summary>
        /// Gets the regular expression used to evaluate a password.
        /// </summary>
        /// <value></value>
        /// <returns>A regular expression used to evaluate a password.</returns>
        public override string PasswordStrengthRegularExpression
        {
            get { return null; }
            //get { return this.GetConfig("passwordStrengthRegularExpression", ""); }
        }

        #endregion UnusedPart

        /// <summary>
        /// Gets the minimum number of special characters that must be present in a valid password.
        /// </summary>
        /// <value></value>
        /// <returns>The minimum number of special characters that must be present in a valid password.</returns>
        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return 1; } 
        }

        /// <summary>
        /// Gets the minimum length required for a password.
        /// </summary>
        /// <value></value>
        /// <returns>The minimum length required for a password. </returns>
        public override int MinRequiredPasswordLength
        {
            get { return 10; }
        } 

        /// <summary>
        /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
        /// </summary>
        /// <value></value>
        /// <returns>true if the membership provider requires a unique e-mail address; otherwise, false. The default is true.</returns>
        public override bool RequiresUniqueEmail
        {
            get { return true;}
        }        

        /// <summary>
        /// Gets a value indicating the format for storing passwords in the membership data store.
        /// </summary>
        /// <value></value>
        /// <returns>MembershipPasswordFormat.Hashed</returns>
        public override MembershipPasswordFormat PasswordFormat
        {
            get { return MembershipPasswordFormat.Hashed; }
        }

        /// <summary>
        /// Adds a new membership user to the data source.
        /// </summary>
        /// <param name="username">The user name for the new user.</param>
        /// <param name="password">The password for the new user.</param>
        /// <param name="email">The e-mail address for the new user.</param>
        /// <param name="passwordQuestion">The password question for the new user.</param>
        /// <param name="passwordAnswer">The password answer for the new user</param>
        /// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
        /// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
        /// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"/> enumeration value indicating whether the user was created successfully.</param>
        /// <returns>User </returns>
        public override User CreateNewUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, string fullName, int roleID, int teamID, int userSettingsID, out MembershipCreateStatus status)
        {
            // Check username
            if (string.IsNullOrEmpty(username) || username.Length > 100)
            {
                status = MembershipCreateStatus.InvalidUserName;
                return null;
            }

            // Check if password meets complexivity requirements
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
            OnValidatingPassword(args);
            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            // Check e-mail
            if (!IsEmail(email))
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }
            email = email.ToLower();
            
            string passwordHash = ComputeSHA512(password);

            DbParameter dbLogin = CreateParameter("login", username);
            DbParameter dbPassword = CreateParameter("password", passwordHash);
            DbParameter dbemail = CreateParameter("email", email);
            DbParameter dbenabled = CreateParameter("enabled", isApproved);
            DbParameter dbfullname = CreateParameter("fullname", fullName);
            DbParameter dbroleID = CreateParameter("roleID", roleID);
            DbParameter dbteamID = CreateParameter("teamID", teamID);
            DbParameter dbuserSettingsID = CreateParameter("userSettingsID", userSettingsID);

            
            // Insert to database
            try
            {
                connection.Open();

                int pocet = ExecuteNonQuery("user/CreateUser", dbLogin, dbPassword, dbemail, dbenabled, dbfullname, dbroleID, dbteamID, dbuserSettingsID);

                if (pocet == 0) 
                    status = MembershipCreateStatus.UserRejected;
                else 
                    status = MembershipCreateStatus.Success;
                
            }
            finally 
            { 
                connection.Close();
            }
            return null;
        }

        /// <summary>
        /// Creates the user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        public override User CreateUser(User user)
        {
            if(user.Password == string.Empty)
            {
                user.Password = GeneratePassword();
            }
            string passwordHash = ComputeSHA512(user.Password);

            DbParameter dbLogin = CreateParameter("login", user.Login);
            DbParameter dbPassword = CreateParameter("password", passwordHash);
            DbParameter dbemail = CreateParameter("email", user.Email);
            DbParameter dbenabled = CreateParameter("enabled", user.Enabled);
            DbParameter dbfullname = CreateParameter("fullname", user.FullName);
            DbParameter dbroleID = CreateParameter("default_role_ID", user.DefaultRoleID);
            DbParameter dbtelephone = CreateParameter("telephone", user.Telephone);
            DbParameter dbIM = CreateParameter("IM", user.IM);
            DbParameter dbvelocity = CreateParameter("velocity", user.Velocity);
            DbParameter dbdescription = CreateParameter("description", user.Description);

            // Insert to database
            try
            {
                connection.Open();

                int pocet = ExecuteNonQuery("user/CreateUser", dbLogin, dbPassword, dbemail, dbenabled, dbfullname, dbroleID, dbtelephone, dbIM, dbvelocity, dbdescription);

                if (pocet != 1)
                {
                    throw new Exception("User NOT created!");
                }
                user.ID = Convert.ToInt32(GetScalar("user/GetLastInsertedUserID"));
                
            }
            finally
            {
                connection.Close();
            }
            return user;
        }

        /// <summary>
        /// Gets actual user
        /// </summary>
        /// <returns>Actual user</returns>
        public override User GetUser()
        {
            return SCRUM.Core.DataProviders.ProviderFactory.Provider.GetUserByLogin((System.Web.Security.Membership.GetUser() as User).Login);
        }

        /// <summary>
        /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <param name="username">The name of the user to get information for.</param>
        /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
        /// <returns> </returns>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            // Validate arguments
            if (string.IsNullOrEmpty(username) || username.Length > 100) return null;
            username = username.ToLower();

            DbParameter dbusername = CreateParameter("username", username);
          
            try
            {
                connection.Open();
                DbDataReader r = GetVector("/user/GetUser", dbusername);
                if (!r.Read()) return null;
                User u = this.GetUserFromReader(r);
                return (MembershipUser)u;
            }
            finally
            {
                connection.Close();
            }
        }

      
        /// <summary>
        /// Gets the user name associated with the specified e-mail address.
        /// </summary>
        /// <param name="email">The e-mail address to search for.</param>
        /// <returns>
        /// The user name associated with the specified e-mail address. If no match is found, return null.
        /// </returns>
        public override string GetUserNameByEmail(string email)
        {
            DbParameter dbEmail = CreateParameter("email", email);

            try
            {
                connection.Open();
                DbDataReader r = GetVector("user/GetUserNameByEmail", dbEmail);

                if (r.NextResult())
                {
                    return GetStringByColumn(r, "LOGIN");
                }
                else return null;
            }
            finally
            {
                connection.Close();
            }
        }

        /// <summary>
        /// Gets the user by ID.
        /// </summary>
        /// <param name="userID">The user ID.</param>
        /// <returns></returns>
        public override User GetUserByID(int userID)
        {
            User u = new User();
            DbParameter dbID = CreateParameter("userID", userID);

            try
            {
                connection.Open();
                DbDataReader r = GetVector("user/GetUserByID", dbID);
                if (r.Read() != null)
                {
                    u = GetUserFromReader(r);
                }
            }
            finally
            {
                connection.Close();
            }
            return u;
        }

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <param name="username">The name of the user to validate.</param>
        /// <param name="password">The password for the specified user.</param>
        /// <returns>
        /// true if the specified username and password are valid; otherwise, false.
        /// </returns>
        public override bool ValidateUser(string username, string password)
        {
            // Validate arguments
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || username.Length > 100) return false;
            username = username.ToLower();
    
            User u = (User) GetUser(username,false);

            if(u == null)
                return false;
            else
            {
                if (!ComputeSHA512(password).Equals(u.Password, StringComparison.InvariantCultureIgnoreCase)) 
                //if(password != u.Password) //zatial nehashujeme
                    return false; // invalid password
                if (u.Enabled == false)
                    return false; // disabled user
            }
            return true;           
        }

        /// <summary>
        /// Changes the password.
        /// </summary>
        /// <param name="login">The login.</param>
        /// <param name="oldPassword">The old password.</param>
        /// <param name="newPassword">The new password.</param>
        /// <param name="status">The status.</param>
        /// <returns></returns>
        public override bool ChangePassword(string login, string oldPassword, string newPassword, out string status)
        {
            //if heslo splnuje poziadavky
            if (String.IsNullOrEmpty(newPassword))
            {
                status = "Empty";
                return false;
            }

            if(!ValidateUser(login, oldPassword))
            {
                status = "InvalidPassword";
                return false;
            }
            
            string passwordHash = ComputeSHA512(newPassword);  
            DbParameter dbLogin = CreateParameter("login", login);
            DbParameter dbPassword = CreateParameter("password", passwordHash);
            int success;

            try
            {                
                connection.Open();
                success = ExecuteNonQuery("user/SetPassword", dbLogin, dbPassword);
                if (success != 0)
                {
                    status = "ok";
                    return true;
                }
                else
                {
                    status = "fail";
                    return false;
                }
            }
            finally
            {
                connection.Close();
            }
        }

        /// <summary>
        /// Resets a user's password to a new, automatically generated password.
        /// </summary>
        /// <param name="username">The user to reset the password for.</param>
        /// <param name="answer">The password answer for the specified user.</param>
        /// <returns>The new password for the specified user.</returns>
        public override string ResetPassword(string username, string answer)
        {
            throw new ProviderException("este neni");
        }

        public override bool ResetPasswordByAdmin(int userID, string newPassword)
        {
            //string newPassword = GeneratePassword();
            string passwordHash = ComputeSHA512(newPassword); 
            DbParameter dbuserID = CreateParameter("userID", userID);
            DbParameter dbPassword = CreateParameter("password", passwordHash);
            int success;

            try
            {
                connection.Open();
                success = ExecuteNonQuery("user/SetPassword", dbuserID, dbPassword);
            }
            finally
            {
                connection.Close();
            }
            return success != 0;
        }

        /// <summary>
        /// Generates the random password.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected string GeneratePassword()
        {
            int passwordLength = 8;
            string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789!@$?_-";
            char[] chars = new char[passwordLength];
            Random rd = new Random();

            for (int i = 0; i < passwordLength; i++)
            {
                chars[i] = allowedChars[rd.Next(0, allowedChars.Length)];
            }

            return new string(chars);
        }


        /// <summary>
        /// Locks the user.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <returns><c>true</c> if user was now locked, <c>false</c> otherwise.</returns>
        public override bool LockUser(string username)
        {
            if (!CheckUserExists(username))
                return false;

            DbParameter dbUsername = CreateParameter("login", username);
            int success;

            try
            {
                connection.Open();
                success = ExecuteNonQuery("user/lockUser", dbUsername);
                return success != 0;
            }
            finally
            {
                connection.Close();
            }
        }

        /// <summary>
        /// Unlocks the user.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <returns><c>true</c> if user was now unlocked, <c>false</c> otherwise.</returns>
        public override bool UnlockUser(string username)
        {
            if (!CheckUserExists(username))
                return false;

            DbParameter dbUsername = CreateParameter("login", username);
            int success;

            try
            {
                connection.Open();
                success = ExecuteNonQuery("user/unlockUser", dbUsername);
                return success != 0;
            }
            finally
            {
                connection.Close();
            }
        }

        /// <summary>
        /// Removes a user from the membership data source.
        /// </summary>
        /// <param name="username">The name of the user to delete.</param>
        /// <param name="deleteAllRelatedData">true to delete data related to the user from the database; false to leave data related to the user in the database.</param>
        /// <returns>
        /// <c>true</c> if the user was successfully deleted; otherwise, <c>false</c>.
        /// </returns>
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            return LockUser(username);
        }

       
        /// <summary>
        /// Gets a collection of membership users where the user name contains the specified user name to match.
        /// </summary>
        /// <param name="usernameToMatch">The user name to search for.</param>
        /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">The total number of matched users.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
        /// </returns>
        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new ProviderException("neni"); 
        }

        /// <summary>
        /// Gets a collection of all the users in the data source in pages of data.
        /// </summary>
        /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex"/> is zero-based.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">The total number of matched users.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUserCollection"/> collection that contains a page of <paramref name="pageSize"/><see cref="T:System.Web.Security.MembershipUser"/> objects beginning at the page specified by <paramref name="pageIndex"/>.
        /// </returns>
        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new ProviderException("neni"); 
        }

        /// <summary>
        /// Updates information about a user in the data source.
        /// </summary>
        /// <param name="user">A <see cref="T:System.Web.Security.MembershipUser"/> object that represents the user to update and the updated information for the user.</param>
        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Updates information about a user in the data source.
        /// </summary>
        /// <param name="user">A <see cref="T:System.Web.Security.MembershipUser"/> object that represents the user to update and the updated information for the user.</param>
        public override void UpdateUser(User user)
        {
            DbParameter dbuserID = CreateParameter("userID", user.ID);
            DbParameter dbLogin = CreateParameter("login", user.Login);
            DbParameter dbemail = CreateParameter("email", user.Email);
            DbParameter dbenabled = CreateParameter("enabled", user.Enabled);
            DbParameter dbfullname = CreateParameter("fullname", user.FullName);
            DbParameter dbroleID = CreateParameter("default_role_ID", user.DefaultRoleID);
            DbParameter dbtelephone = CreateParameter("telephone", user.Telephone);
            DbParameter dbIM = CreateParameter("IM", user.IM);
            DbParameter dbvelocity = CreateParameter("velocity", user.Velocity);
            DbParameter dbdescription = CreateParameter("description", user.Description);

            try
            {
                connection.Open();
                ExecuteNonQuery("user/UpdateUser", dbuserID, dbLogin, dbemail, dbenabled, dbfullname, dbroleID, dbtelephone, dbIM, dbvelocity, dbdescription);
            }
            finally
            {
                connection.Close();
            }
        }


        /// <summary>
        /// Check if user with username exist in db
        /// </summary>
        /// <param name="username">user's login in database</param>
        /// <returns>true, if username(login) exist in db</returns>
        public bool CheckUserExists(string username)
        {
            if (string.IsNullOrEmpty(username)) return false;
            if (username.Length > 100) throw new ArgumentOutOfRangeException("username", "Maximum length of 100 characters exceeded");

            DbParameter dbusername = CreateParameter("username", username);
            try
            {
                connection.Open();
                int pocet = (int) GetScalar("user/CheckUserExist", dbusername);
                
                return pocet == 1;
               
            }
            finally
            {
                connection.Close();
            } 
        }

        /// <summary>
        /// Check if email has valid pattern
        /// </summary>
        /// <param name="email">email</param>
        /// <returns></returns>
        private static bool IsEmail(string email)
        {
            if (string.IsNullOrEmpty(email) || email.Length > 100) return false;
            return System.Text.RegularExpressions.Regex.IsMatch(email, EmailPattern);
        }
        
        /// <summary>
        /// Gets all information about user from data reader
        /// </summary>
        /// <param name="reader">data reader</param>
        /// <returns>User with properties</returns>
        private User GetUserFromReader(DbDataReader reader)
        {
            User u = new User();
            u.ID = System.Convert.ToInt32(reader["ID"]);
            u.Login = System.Convert.ToString(reader["Login"]);
            u.Password = System.Convert.ToString(reader["Password"]);
            u.Email = System.Convert.ToString(reader["Email"]);
            u.Enabled = System.Convert.ToBoolean(reader["Enabled"]);
            u.FullName = System.Convert.ToString(reader["FullName"]);
            return u; 
        }
        
        /// <summary>
        /// Computes hash for password
        /// </summary>
        /// <param name="s">unhashed password</param>
        /// <returns>hashed password</returns>
        private static string ComputeSHA512(string s)
        {
            if (string.IsNullOrEmpty(s)) throw new ArgumentNullException();
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(s);
            buffer = System.Security.Cryptography.SHA512Managed.Create().ComputeHash(buffer);
            return System.Convert.ToBase64String(buffer).Substring(0, 86); // strip padding
        }

        #region NotImplemented

        public override bool ChangePassword(string username, string password, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        #endregion NotImplemented

        /// <summary>
        /// Gets the users which can be assigned to the task.
        /// </summary>
        /// <param name="taskID">The task ID.</param>
        /// <returns></returns>
        public override IList<User> GetUsersExceptTaskUsers(int taskID)
        {
            List<User> users = new List<User>();
            DbParameter dbtaskID = CreateParameter("taskID", taskID);
            try
            {
                connection.Open();                

                DbDataReader data = GetVector("user/GetUsersExceptTaskUsers2", dbtaskID);

                while (data.Read())
                {
                    User user = new User();
                    user.ID = (int)data["ID"];
                    user.Login = (string)data["Login"];
                    user.FullName = (string)data["FullName"];
                    user.Enabled = (bool)data["Enabled"];
                    user.Password = (string)data["Password"];
                    user.Email = (string)data["Email"];

                    users.Add(user);
                }
            }
            finally
            {
                connection.Close();
            }
            return users;
        }

        /// <summary>
        /// Determines whether the specified user with ID is admin.
        /// </summary>
        /// <param name="userID">The user ID.</param>
        /// <returns>
        /// 	<c>true</c> if the specified user ID is admin; otherwise, <c>false</c>.
        /// </returns>
        public override bool IsAdmin(int userID)
        {
            bool found = false;
            DbParameter dbID = CreateParameter("userID", userID);

            try
            {
                connection.Open();
                int count = (int)GetScalar("user/GetAdminByID", dbID);

                found = count == 1;
            }
            finally
            {
                connection.Close();
            }
            return found;
        }

        /// <summary>
        /// Gets the user profile ID.
        /// </summary>
        /// <param name="defaultRoleID">ID of profile User.</param>
        /// <returns></returns>
        public override int GetUserProfileID(int defaultRoleID)
        {
            int userID = -1;

            DbParameter dbroleID = CreateParameter("roleID", defaultRoleID);
            try
            {
                connection.Open();

                userID = (int)GetScalar("user/GetUserProfileID", dbroleID);                
            }
            finally
            {
                connection.Close();
            }
            return userID;
        }
         
    }
}

